#ifndef GPUBUFFERMANIPULATOR_H
#define GPUBUFFERMANIPULATOR_H

#include "Log.h"
#include "Exception.h"
#include "GpuBuffer.h"

namespace Cvekas {

/// Allows to access individual elements of GpuBuffer and manipulate them.
template <typename T>
class GpuBufferManipulator
{
public:
	/// Constructor
	/// \param buffer Pointer to buffer
	GpuBufferManipulator(GpuBufferPtr buffer)
		:
	buffer(buffer),
	second_buffer(NULL),
	use_second_buffer(false)
	{
		LOG(FORMAT("Constructing manipulator for gpu buffer %s", % buffer->getName()));

		size = buffer->getSizeInBytes();
		item_size = sizeof(T);
		
		if(size % item_size != 0)
			throw EXCEPTION("Buffer size is not multiple of item size!");

		if(buffer->isDynamic())
		{
			use_second_buffer = true;
			second_buffer = new T[size / item_size];
		}
	}

	/// Destructor
	virtual ~GpuBufferManipulator()
	{
		if(buffer->isLocked())
		{
			LOG("Buffer still locked on destruction of manipulator. Did you forget to apply() changes?");
			unlock();
		}

		if(use_second_buffer)
			delete[] second_buffer;

		LOG(FORMAT("Manipulator for gpu buffer %s destroyed", % buffer->getName()));
	}

	/// Sets element.
	void set(uint i, const T& item)
	{
		// Should throw an exception if index is out of bounds, but that would be very slow
		if(use_second_buffer)
		{
			second_buffer[i] = item;
		}
		else
		{
			if(!buffer->isLocked())
				lock();

			T* items = (T*)locked.data;
			items[i] = item;
		}
	}

	/// Returns element.
	T get(uint i)
	{
		// Should throw an exception if index is out of bounds, but that would be very slow
		if(use_second_buffer)
		{
			return second_buffer[i];
		}
		else
		{
			if(!buffer->isLocked())
				lock();

			T* items = (T*)locked.data;
			return items[i];
		}
	}

	/// Applies changes. After calling apply buffer is ready to be used.
	void apply()
	{
		if(use_second_buffer)
		{
			lock();
			memcpy(locked.data, second_buffer, size);
			unlock();
		}
		else
		{
			if(buffer->isLocked())
				unlock();
		}
	}

private:
	void lock()
	{
		locked = buffer->lock(false);
	}

	void unlock()
	{
		buffer->unlock();
	}

	GpuBufferPtr buffer;
	bool use_second_buffer;

	LockedBuffer locked;
	int size, item_size;

	T* second_buffer;
};

} // namespace

#endif